package com.study.goal.service.impl;

import cn.hutool.core.bean.BeanUtil;
import com.baomidou.mybatisplus.core.conditions.query.QueryWrapper;
import com.baomidou.mybatisplus.core.conditions.update.UpdateWrapper;
import com.study.common.core.page.TableDataInfo;
import com.study.goal.domain.GoalAssignment;
import com.study.goal.domain.Plan;
import com.study.goal.domain.PlanOccurrence;
import com.study.goal.domain.bo.PlanOccurrenceBo;
import com.study.goal.mapper.GoalAssignmentMapper;
import com.study.goal.mapper.PlanMapper;
import com.study.goal.mapper.PlanOccurrenceMapper;
import com.study.goal.service.IPlanOccurrenceService;
import lombok.RequiredArgsConstructor;
import lombok.extern.slf4j.Slf4j;
import org.jacoco.agent.rt.internal_b6258fc.core.internal.flow.IFrame;
import org.springframework.stereotype.Service;
import org.springframework.transaction.annotation.Transactional;

import java.time.DayOfWeek;
import java.time.LocalDate;
import java.time.LocalDateTime;
import java.time.LocalTime;
import java.util.*;
import java.util.stream.Collectors;

@Slf4j
@RequiredArgsConstructor
@Service
public class IPlanOccurrenceServiceImpl implements IPlanOccurrenceService {

    private final PlanMapper planMapper;

    private final GoalAssignmentMapper assignmentMapper;
    private final PlanOccurrenceMapper planOccurrenceMapper;

    private static final int BATCH_SIZE = 200;

    @Override
    public Boolean create(PlanOccurrenceBo occurrence) {
        PlanOccurrence planOccurrence = BeanUtil.toBean(occurrence, PlanOccurrence.class);
        planOccurrence.setStatus("SCHEDULED");
        planOccurrence.setCreateTime(LocalDateTime.now());
        planOccurrence.setUpdateTime(LocalDateTime.now());
        return planOccurrenceMapper.insert(planOccurrence) > 0;
    }

    @Override
    public Boolean start(Long id) {
        return updateStatus(id, "STARTED");
    }

    @Override
    public Boolean submit(Long id) {
        return updateStatus(id, "SUBMITTED");
    }

    @Override
    public Boolean approve(Long id) {
        return updateStatus(id, "APPROVED");
    }

    @Override
    public Boolean reject(Long id) {
        return updateStatus(id, "REJECTED");
    }

    @Override
    public Boolean markMissed(Long id) {
        return updateStatus(id, "MISSED");
    }

    @Override
    public TableDataInfo<PlanOccurrence> listByAssignment(Long assignmentId) {
        return TableDataInfo.build(planOccurrenceMapper.selectList(
                new QueryWrapper<PlanOccurrence>()
                        .eq("assignment_id", assignmentId)
                        .orderByAsc("scheduled_at")
        ));
    }

    @Override
    public List<PlanOccurrence> listByAssignmentAndDate(Long assignmentId, LocalDateTime date) {
        LocalDate localDate = date.toLocalDate();
        LocalDateTime start = localDate.atStartOfDay();
        LocalDateTime end = localDate.plusDays(1).atStartOfDay();
        return planOccurrenceMapper.selectList(
                new QueryWrapper<PlanOccurrence>()
                        .eq("assignment_id", assignmentId)
                        .between("scheduled_at", start, end)
                        .orderByAsc("scheduled_at")
        );
    }

    @Override
    public List<PlanOccurrence> studentListByDate(Long userId, LocalDateTime date) {
        List<GoalAssignment> goalAssignments = assignmentMapper.selectList(
                new QueryWrapper<GoalAssignment>()
                        .eq("student_id", userId)
                        .eq("status", "IN_PROGRESS")
                        .or()
                        .eq("status", "ASSIGNED"));

        // 修改以下部分
        LocalDate localDate = date.toLocalDate();
        LocalDateTime start = localDate.atStartOfDay();           // 当天开始时间
        LocalDateTime end = localDate.plusDays(1).atStartOfDay(); // 次日开始时间

        List<PlanOccurrence> occurrences = planOccurrenceMapper.selectList(
                new QueryWrapper<PlanOccurrence>()
                        .in("assignment_id", goalAssignments.stream().map(GoalAssignment::getId).collect(Collectors.toList()))
                        .between("scheduled_at", start, end)      // 使用 between 查询范围
                        .orderByAsc("scheduled_at"));

        return occurrences;
    }

    @Transactional(rollbackFor = Exception.class)
    @Override
    public void generateOccurrencesForAssignment(Long assignmentId, boolean generateFullRange, int days) {
        GoalAssignment assignment = assignmentMapper.selectById(assignmentId);
        if (assignment == null) {
            throw new RuntimeException("GoalAssignment not found");
        }

        // 1. 查询该 goalId 下所有有效计划
        List<Plan> plans = planMapper.selectList(
                new QueryWrapper<Plan>()
                        .eq("goal_id", assignment.getGoalId())
                        .eq("status", "PENDING"));

        LocalDate today = LocalDate.now();

        List<PlanOccurrence> toSave = new ArrayList<>();

        for (Plan plan : plans) {

            //解析计划时间范围
            LocalDate start = plan.getStartDate() != null ? plan.getStartDate() : today;
            LocalDate end = plan.getEndDate() != null ? plan.getEndDate() : today.plusYears(1);

            LocalDate genStart;
            LocalDate genEnd;

            if (generateFullRange) {
                genStart = start;
                genEnd = end;
            } else {
                genStart = today.isAfter(start) ? today : start;
                genEnd = today.plusDays(days);
                if (genEnd.isAfter(end)) {
                    genEnd = end;
                }
            }

            // 处理 executeTime = java.sql.Time
            LocalTime execTime = plan.getExecuteTime() != null
                    ? plan.getExecuteTime().toLocalTime()
                    : LocalTime.of(8, 0);

            boolean isDaily = "每天".equals(plan.getFrequency());
            Set<DayOfWeek> allowedDays = parseFrequencyToDays(plan.getFrequency());

            //遍历日期，按照计划的频率生成计划执行记录
            LocalDate cut = genStart;
            while (!cut.isAfter(genEnd)) {
                boolean need = isDaily || allowedDays.contains(cut.getDayOfWeek());
                if (need) {
                    LocalDateTime scheduledAt = LocalDateTime.of(cut, execTime);
                    //幂等检查：是否已存在（plan_id, assignment_id, scheduled_at）
                    Integer cnt = planOccurrenceMapper.selectCount(new QueryWrapper<PlanOccurrence>()
                            .eq("plan_id", plan.getId())
                            .eq("assignment_id", assignmentId)
                            .eq("scheduled_at", scheduledAt)).intValue();

                    if (cnt == null || cnt == 0) {
                        PlanOccurrence occurrence = new PlanOccurrence();
                        occurrence.setPlanId(plan.getId());
                        occurrence.setAssignmentId(assignmentId);
                        occurrence.setScheduledAt(scheduledAt);
                        occurrence.setStatus("SCHEDULED");
                        occurrence.setCreateTime(LocalDateTime.now());
                        occurrence.setUpdateTime(LocalDateTime.now());
                        toSave.add(occurrence);
                    }
                }
                cut = cut.plusDays(1);
            }
        }

        // 批量保存
        if (!toSave.isEmpty()) {
            planOccurrenceMapper.insertBatch(toSave, BATCH_SIZE);
        }
    }

    @Override
    public void generateOccurrencesForAllAssignments(int days) {
        List<GoalAssignment> assignments = assignmentMapper.selectList(
                new QueryWrapper<GoalAssignment>().in("status", Arrays.asList("ASSIGNED", "ACTIVE"))
        );
        for (GoalAssignment a : assignments) {
            generateOccurrencesForAssignment(a.getId(), false, days);
        }
    }

    @Transactional(rollbackFor = Exception.class)
    @Override
    public int markMissedOccurrences() {
        LocalDateTime now = LocalDateTime.now();
        UpdateWrapper<PlanOccurrence> uw = new UpdateWrapper<>();
        uw.in("status", Arrays.asList("SCHEDULED", "STARTED"))
                .lt("scheduled_at", now)
                .set("status", "MISSED")
                .set("update_time", now);
        return planOccurrenceMapper.update(null, uw);
    }


    /**
     * 更新计划执行状态
     *
     * @param id
     * @param status
     * @return
     */
    private Boolean updateStatus(Long id, String status) {
        PlanOccurrence occurrence = planOccurrenceMapper.selectById(id);
        if (occurrence == null) {
            throw new RuntimeException("PlanOccurrence not found");
        }
        occurrence.setStatus(status);
        occurrence.setUpdateTime(LocalDateTime.now());
        return planOccurrenceMapper.updateById(occurrence) > 0;
    }


    private Set<DayOfWeek> parseFrequencyToDays(String frequency) {
        Set<DayOfWeek> set = new HashSet<>();
        if (frequency == null) {
            return set;
        }
        if (frequency.contains("每天")) {
            for (DayOfWeek d : DayOfWeek.values()) set.add(d);
            return set;
        }
        // 例如 "周一、周三、周五"
        String[] parts = frequency.split("、");
        for (String p : parts) {
            p = p.trim();
            switch (p) {
                case "周一":
                    set.add(DayOfWeek.MONDAY);
                    break;
                case "周二":
                    set.add(DayOfWeek.TUESDAY);
                    break;
                case "周三":
                    set.add(DayOfWeek.WEDNESDAY);
                    break;
                case "周四":
                    set.add(DayOfWeek.THURSDAY);
                    break;
                case "周五":
                    set.add(DayOfWeek.FRIDAY);
                    break;
                case "周六":
                    set.add(DayOfWeek.SATURDAY);
                    break;
                case "周日":
                    set.add(DayOfWeek.SUNDAY);
                    break;
                default:
                    break;
            }
        }
        return set;
    }

}
